跳至主要内容

課程:JavaScript 與 React 底層原理 第 8 堂:ES6+ 語法基礎

25 箭頭函數語法

在 2015 年 ES6 推出之前,JavaScript 的 function 關鍵字一直處於一種「身份認同危機」中。它既要擔任一般函數的角色,又要作為類別的建構函數(Constructor),同時還要處理極其混亂的 this 動態綁定問題。這導致開發者經常需要寫出 var self = this; 這種令人困惑的程式碼。

箭頭函數(Arrow Functions)的出現,不僅是為了少打幾個字,更是為了徹底解決這些設計上的遺留問題。在 React 的世界裡,箭頭函數幾乎已經成為標準,從定義元件到處理事件,它無處不在。但在你開始大量使用它之前,我們必須釐清:它與傳統函數之間,除了外型,到底還有哪些本質上的不同?

語法演進:從冗長到極簡

箭頭函數最吸引人的地方在於其極致的簡潔性。我們可以將一個傳統函數逐步重構成最精簡的箭頭函數。

隱式回傳(Implicit Return)的威力

想像我們有一個簡單的平方計算函數:

// 1. 傳統函數寫法
function square(x) {
return x * x;
}

// 2. 基本箭頭函數寫法
const square = (x) => {
return x * x;
};

// 3. 如果只有一個參數,可以省略括號
const square = x => {
return x * x;
};

// 4. 隱式回傳:如果函數體只有一個運算式,可以省略大括號與 return 關鍵字
const square = x => x * x;

這種「隱式回傳」在 React 處理資料轉換(如 map)時非常強大。例如,將一組數字轉化為 JSX 列表:

const numbers = [1, 2, 3];
const listItems = numbers.map(n => <li key={n}>{n}</li>);

與傳統寫法相比,這種線性、宣告式(Declarative)的寫法讓邏輯一目了然。

重點陷阱:回傳物件字面量

這是初學者最常踩到的坑。假設我們想用 map 將一組 ID 轉換成物件陣列:

const ids = [101, 102, 103];

// ❌ 錯誤寫法
const users = ids.map(id => { id: id });
// 結果會是 [undefined, undefined, undefined]

為什麼會失敗?因為 JavaScript 引擎在解析時,會把箭頭後面的 { 視為函數體的開頭(Block),而不是物件的開頭。在上面的錯誤範例中,引擎認為你寫了一個標籤為 id: 的程式碼區塊,且沒有回傳任何值。

正確解法: 用小括號將物件包裹起來,告訴引擎「這是一個完整的運算式(Expression)」:

// ✅ 正確寫法
const users = ids.map(id => ({ id: id }));

在 React 的 useState 函數式更新中,如果你需要更新一個物件狀態,這個括號至關重要:

setUser(prev => ({ ...prev, name: 'Aria' })); // 必須加小括號

行為差異:為什麼它不只是縮寫?

箭頭函數與一般函數(Function Declaration/Expression)在底層運作上有著顯著差異。我們可以說箭頭函數是一種「輕量化」的函數。

沒有自己的 this(Lexical This)

這可能是箭頭函數最重要的特性。回顧我們在 Topic 3.5 學過的 this 綁定規則,一般函數的 this 是在呼叫時決定的(動態綁定)。而箭頭函數則完全沒有自己的 this,它會透過**作用域鏈(Scope Chain)**向外查找,捕獲「定義時」所在環境的 this

我們來看一個經典的計時器問題:

const Counter = {
count: 0,
start: function() {
// 一般函數
setTimeout(function() {
console.log(this.count); // 這裡的 this 會指向全域或 undefined
}, 1000);
}
};

const BetterCounter = {
count: 0,
start: function() {
// 箭頭函數捕獲了 start 方法執行時的 this (即 BetterCounter)
setTimeout(() => {
console.log(this.count);
}, 1000);
}
};

在 React 的 Class Component 時代,我們必須在 constructor 裡寫 this.handleClick = this.handleClick.bind(this),就是為了防止事件觸發時 this 丟失。而現在我們全面轉向 Hooks,雖然 this 的出場率降低了,但理解箭頭函數這種「繼承外層環境」的特性,對於理解閉包(Closure)與 Hooks 的行為依然非常有幫助。

沒有 arguments 物件

在一般函數中,你可以存取一個名為 arguments 的類陣列物件,裡面包含了所有傳入的參數。但箭頭函數並不提供這個物件。

const showArgs = () => {
console.log(arguments); // ReferenceError: arguments is not defined
};

為什麼要這樣設計?因為 ES6 同步推出了 Rest 參數(**...args**,這才是處理剩餘參數的現代化解決方案(我們在上一部分剛學過)。箭頭函數拋棄了過時的 arguments,鼓勵你使用更直觀的 Rest 語法。

不能作為建構函數(Constructor)

一般函數內部具備一個 [[Construct]] 方法,可以用 new 來實例化一個物件。但箭頭函數沒有這個機制,它也沒有自己的 prototype 屬性。

const Person = (name) => { this.name = name; };
const aria = new Person('Aria'); // TypeError: Person is not a constructor

這體現了 JS 語言設計的進化:函數就該單純負責邏輯運算,如果你需要建立物件模型,應該使用 class 或物件委派。

React 中的標準應用:為什麼它是標配?

在現代 React 開發中,箭頭函數已經是預設選擇,這不僅是因為簡潔,更因為它與 React 的「函數式」思維完美契合。

1. 元件定義風格

雖然 function App() {} 依然有效,但現在社群主流更偏向使用 const 搭配箭頭函數來定義元件:

const UserProfile = ({ name, role }) => {
return (
<div>
<h1>{name}</h1>
<p>{role}</p>
</div>
);
};

使用 const 定義元件有幾個隱藏好處:

  • 避免 Hoisting 誤用:正如我們在 Topic 1.4 學到的,const 宣告的函數不會像 function 宣告那樣被提升到作用域頂端。這強迫開發者必須「先定義,後使用」,讓程式碼結構更清晰。
  • 一致性:當你的元件、Hooks 與輔助函式都使用 const 宣告時,整份檔案的視覺風格高度統一。

2. 事件處理器(Event Handlers)

在 Hooks 環境下,我們經常在 useEffect 或事件監聽中定義回調函數。箭頭函數讓參數傳遞變得非常直觀:

const ProductList = () => {
const handleDelete = (id) => {
console.log(`Deleting product ${id}`);
};

return (
<button onClick={() => handleDelete(101)}>
刪除產品
</button>
);
};

在這裡,() => handleDelete(101) 建立了一個閉包,它「記住」了當下的參數 101。這種寫法比以前的 bind 或複雜的屬性查找要簡單得多。

3. 函數式更新(Functional Updates)

在使用 useState 時,如果新的狀態依賴於舊的狀態,官方強烈建議傳入一個「更新函數」而非直接傳入數值:

const [count, setCount] = useState(0);

// 使用箭頭函數簡寫,邏輯非常線性:給予 prev,回傳 prev + 1
const increment = () => setCount(prev => prev + 1);

這種「資料轉換」的語意,透過箭頭函數的隱式回傳表達得極其優美。

總結:箭頭函數的定位

箭頭函數是 JavaScript 為了「現代化」而做的減法。它去掉了複雜的 this 規則,去掉了笨重的 prototype,去掉了過時的 arguments,只留下了最純粹的邏輯。

在 React 開發中,箭頭函數讓我們的程式碼更簡潔、邏輯更線性,它是我們操作不可變資料(Immutability)與處理副作用(Effects)的最佳拍檔。

重點回顧

  • 隱式回傳:單行運算式不需要 return
  • 物件字面量陷阱:回傳物件時別忘了小括號 ({ key: value })
  • Lexical This:沒有自己的 this,捕獲定義時的上下文。
  • 無建構能力:不能使用 new

接下來的學習安排

到這裡,我們已經完整掌握了 ES6+ 中最核心的三大語法支柱:

  1. 解構賦值:讓我們優雅地從 Props 與 State 中提取資料。
  2. Spread 與 Rest:讓我們能以不可變的方式更新狀態,並靈活處理剩餘屬性。
  3. 箭頭函數:為我們提供了簡潔且語意明確的元件與事件處理定義方式。

這三者經常結伴出現,例如在 React 中常見的這種寫法: const MyComponent = ({ id, ...rest }) => <div onClick={() => update(id)} {...rest} /> 現在你應該能一眼看穿這行程式碼背後所有的運作邏輯了。

這是本堂課最後一個深入教學的部分。接下來我們將進入本節課的最後階段:「Review」回顧區塊。我們將透過幾個精心設計的問題,測試你是否真的掌握了今天學到的所有細節。確保這些基礎工具已經內化成你的直覺,因為下一堂課我們將繼續完成 Topic 5 的後半段,探討模組系統、非同步處理與陣列的函數式操作,那些內容將會更頻繁地調用今天所學的知識!